package com.tuocent.dagv2.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tuocent.dagv2.common.AjaxResult;
import com.tuocent.dagv2.common.BasicRequest;
import com.tuocent.dagv2.common.entity.LoginUser;
import com.tuocent.dagv2.common.entity.LoginUserInfo;
import com.tuocent.dagv2.common.utils.TCUtil;
import com.tuocent.dagv2.common.utils.redis.RedisCache;
import com.tuocent.dagv2.constant.Constants;
import com.tuocent.dagv2.entity.OperateLogs;
import com.tuocent.dagv2.service.IOperateLogsService;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import eu.bitwalker.useragentutils.Version;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Map;


@Component
@Slf4j
public class AppInterceptor implements HandlerInterceptor {
    @Value("${server.domain}")
    private String domain;

    @Resource
    private RedisCache redis;

    private LoginUserInfo loginUser;
    //请求开始时间标识
    private static final String LOGGER_SEND_TIME = "send_time";

    @Autowired
    private IOperateLogsService operateLogsService;


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        //获取请求URI
        String uri = request.getRequestURI();
        //获取访问IP
        String IP = TCUtil.getClientIp(request);

        // 如果是OPTIONS则结束请求
        response.setHeader("Access-Control-Allow-Origin", domain);
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "86400");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
        String method = request.getMethod().toUpperCase();
        if (!"GET".equals(method) && !"POST".equals(method)) {
            response.setStatus(HttpStatus.NO_CONTENT.value());
            return false;
        }

        //检测用户SESSION
        HttpSession session = request.getSession();
        //如果SESSION 为空，请转到登录页面
        if (session == null) {
            response.sendRedirect("/account/login");
            return false;
        } else {
            if (!UserSecurity(request, response, session, IP)) {
                response.sendRedirect("/account/login");
                return false;
            }
            //开始检测用户
        }


        OperateLogs loggerInfos = new OperateLogs();


        loggerInfos.setUserCode(loginUser.getUserCode());
        //设置客户端IP
        loggerInfos.setIp(IP);
        //设置请求方法
        loggerInfos.setMethod(request.getMethod());
        //设置请求类型(json|普通请求)
        loggerInfos.setRequestType(BasicRequest.getRequestType(request));
        //设置请求地址
        loggerInfos.setUri(uri);
        //设置请求开始时间
        long currentTimeMillis = System.currentTimeMillis();
        loggerInfos.setCreateTime(LocalDateTime.now());
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        Enumeration<String> h = request.getHeaderNames();
        Browser browser = userAgent.getBrowser();
        OperatingSystem operatingSystem = userAgent.getOperatingSystem();
        loggerInfos.setOperatingSystem(operatingSystem.getName());
        loggerInfos.setBrowser(browser.getName());
        Version browserVersion = userAgent.getBrowserVersion();
        loggerInfos.setBrowserVersion(browserVersion != null ? browserVersion.toString() : "");
        request.setAttribute(LOGGER_SEND_TIME, currentTimeMillis);
        //设置请求实体到request中，方便afterCompletion方法调用
        request.setAttribute(Constants.LOGGER_ENTITY, loggerInfos);


        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        if (modelAndView != null) {
            if (modelAndView.getViewName() == "error") {
                ServletWebRequest swr = new ServletWebRequest(request);
                DefaultErrorAttributes defaultErrorAttributes = new DefaultErrorAttributes();
                ErrorAttributeOptions eao = ErrorAttributeOptions.of(
                        ErrorAttributeOptions.Include.BINDING_ERRORS,
                        ErrorAttributeOptions.Include.EXCEPTION,
                        ErrorAttributeOptions.Include.MESSAGE,
                        ErrorAttributeOptions.Include.STACK_TRACE);
                Map<String, Object> map = defaultErrorAttributes.getErrorAttributes(swr, eao);
                ajaxResult(response, AjaxResult.error(Integer.valueOf(map.get("status").toString()).intValue(), map.get("error") + "; Path:" + map.get("path")));
            }
        }
    }

    /**
     * 整个请求处理完毕回调方法，即在视图渲染完毕时回调，
     * 如性能监控中我们可以在此记录结束时间并输出消耗时间，
     * 还可以进行一些资源清理，类似于try-catch-finally中的finally，
     * 但仅调用处理器执行链中
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        //获取请求错误码
        int status = response.getStatus();
        //当前时间
        long currentTimeMillis = System.currentTimeMillis();
        //获取请求开始时间
        long time = (long) request.getAttribute(LOGGER_SEND_TIME);


        //获取请求日志实体
        OperateLogs loggerInfos = (OperateLogs) request.getAttribute(Constants.LOGGER_ENTITY);
        if (loggerInfos != null) {
            //请求参数信息
            String requestTyper = BasicRequest.getRequestType(request);
            if (!requestTyper.equals("JSON")) {
                String paramData = JSON.toJSONString(request.getParameterMap(),
                        SerializerFeature.DisableCircularReferenceDetect, SerializerFeature.WriteMapNullValue);
                loggerInfos.setParamData(paramData);
            } else {
                try {
                    ServletInputStream inputStream = request.getInputStream();
                    loggerInfos.setParamData(IOUtils.toString(inputStream, StandardCharsets.UTF_8));
                } catch (IOException e) {
                    log.info(e.getMessage());
                }

            }
            //设置请求时间差
            loggerInfos.setTimeConsuming(Integer.valueOf((currentTimeMillis - time) + ""));
            //设置返回时间
            loggerInfos.setReturnTime(LocalDateTime.now());
            //设置返回错误码
            loggerInfos.setHttpStatusCode(status + "");
            operateLogsService.save(loggerInfos);
        }
    }

    private void ajaxResult(HttpServletResponse response, AjaxResult result) throws Exception {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        String js = JSON.toJSONString(result);
        try (PrintWriter writer = response.getWriter()) {
            writer.print(js);

        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 用户检测
     * 1.先检测SESSION是否准确
     * 2.然后通过SESSION ID 读取Redis，与Redis的数据作对比 Token是否一至
     * 3.再检测IP是否与Redis上记录的IP相同
     *
     */
    private boolean UserSecurity(HttpServletRequest request, HttpServletResponse response, HttpSession session, String ip) throws Exception {

        String id = session.getId();
        if (redis.exists(id)) {
            loginUser = redis.get(id);
            if (loginUser == null || !ip.equals(loginUser.getIp())) {
                return false;
            }

            if (loginUser.getSpm() == null || session.getAttribute("spm") == null || !session.getAttribute("spm").equals(loginUser.getSpm())) {
                return false;
            }
            redis.set(id, loginUser, 3600L);
            return true;
        } else {
            if ("Application/json".equals(request.getHeader("content-type"))) {
                ajaxResult(response, AjaxResult.error(com.tuocent.dagv2.constant.HttpStatus.FORBIDDEN, "用户登录信息已过期"));
                return false;
            }
            log.error("没有找到用户登录信息");
        }
        return false;
    }
}
